import * as fs from 'fs';
import * as path from 'path';
import axios from 'axios';
const randomUseragent = require('random-useragent');

// 引入 tinify，但使用动态导入避免加载问题
let tinify: any = null;
try {
    tinify = require('tinify');
} catch (error) {
    console.warn('tinify 加载失败，将仅使用爬虫模式:', error);
}

export interface CompressionOptions {
    sourceRoot: string;
    apiKeys: string[];
    replaceOriginal: boolean;
    outputRoot: string;
    fileDelay: number;
    preserveStructure: boolean;
    copyUnsupported: boolean;
    mode: 'api' | 'crawler';
    progress?: (current: number, total: number, message: string) => void;
}

export interface CompressionResult {
    success: number;
    failed: number;
    totalOriginal: number;
    totalCompressed: number;
    savedSize: string;
    savedPercent: string;
    failedFiles: string[];
}

export interface FileResult {
    filePath: string;
    fileName: string;
    originalSize: number;
    compressedSize: number;
    savedPercent: string;
    success: boolean;
    error?: string;
}

export class TinyPngCompressor {
    private readonly SUPPORTED_FORMATS = ['.jpg', '.jpeg', '.png', '.webp'];
    private readonly TINY_API = {
        STORE_URL: 'https://tinypng.com/backend/opt/store',
        PROCESS_URL: 'https://tinypng.com/backend/opt/process',
        HEADERS: {
            Referer: 'https://tinypng.com/',
            Origin: 'https://tinypng.com',
        }
    };

    private currentKeyIndex = 0;

    async compressFolder(options: CompressionOptions): Promise<CompressionResult> {
        const {
            sourceRoot,
            apiKeys,
            replaceOriginal,
            outputRoot,
            fileDelay,
            preserveStructure,
            copyUnsupported,
            mode,
            progress
        } = options;

        // 获取所有图片文件
        const imageFiles = this.getAllImageFiles(sourceRoot);
        
        if (imageFiles.length === 0) {
            throw new Error('没有找到可压缩的图片文件');
        }

        // 初始化 API 模式
        if (mode === 'api' && tinify && apiKeys.length > 0) {
            this.currentKeyIndex = 0;
            tinify.key = apiKeys[0];
            
            try {
                await tinify.validate();
            } catch (error) {
                throw new Error('API Key 验证失败，请检查配置');
            }
        }

        // 创建输出目录
        if (!replaceOriginal && outputRoot && !fs.existsSync(outputRoot)) {
            fs.mkdirSync(outputRoot, { recursive: true });
        }

        // 处理结果统计
        const results: FileResult[] = [];
        let current = 0;

        // 逐个处理文件
        for (const filePath of imageFiles) {
            current++;
            const fileName = path.basename(filePath);
            
            if (progress) {
                progress(current, imageFiles.length, `处理 ${fileName}`);
            }

            try {
                let result: FileResult;
                
                if (mode === 'api' && tinify) {
                    result = await this.compressWithAPI(filePath, options);
                } else {
                    result = await this.compressWithCrawler(filePath, options);
                }
                
                results.push(result);

                // 如果是 API 模式且遇到配额限制，尝试切换 key
                if (mode === 'api' && !result.success && result.error?.includes('429')) {
                    if (await this.switchToNextKey(apiKeys)) {
                        // 重试当前文件
                        result = await this.compressWithAPI(filePath, options);
                        results[results.length - 1] = result;
                    }
                }

            } catch (error) {
                results.push({
                    filePath,
                    fileName,
                    originalSize: 0,
                    compressedSize: 0,
                    savedPercent: '0',
                    success: false,
                    error: error instanceof Error ? error.message : String(error)
                });
            }

            // 文件间延迟
            if (fileDelay > 0 && current < imageFiles.length) {
                await new Promise(resolve => setTimeout(resolve, fileDelay));
            }
        }

        // 复制非图片文件
        if (copyUnsupported && !replaceOriginal && outputRoot) {
            this.copyUnsupportedFiles(sourceRoot, outputRoot, preserveStructure);
        }

        // 计算结果统计
        const successResults = results.filter(r => r.success);
        const failedResults = results.filter(r => !r.success);
        
        const totalOriginal = successResults.reduce((sum, r) => sum + r.originalSize, 0);
        const totalCompressed = successResults.reduce((sum, r) => sum + r.compressedSize, 0);
        const savedSize = totalOriginal - totalCompressed;
        const savedPercent = totalOriginal > 0 ? ((savedSize / totalOriginal) * 100).toFixed(1) : '0';

        return {
            success: successResults.length,
            failed: failedResults.length,
            totalOriginal,
            totalCompressed,
            savedSize: this.formatFileSize(savedSize),
            savedPercent,
            failedFiles: failedResults.map(r => r.filePath)
        };
    }

    private async compressWithAPI(filePath: string, options: CompressionOptions): Promise<FileResult> {
        if (!tinify) {
            throw new Error('tinify 库未加载');
        }

        const fileName = path.basename(filePath);
        const originalSize = fs.statSync(filePath).size;

        try {
            const source = tinify.fromFile(filePath);
            
            let outputPath: string;
            
            if (options.replaceOriginal) {
                // 替换模式：先压缩到临时文件
                const tempPath = filePath + '.tmp';
                await source.toFile(tempPath);
                
                const compressedSize = fs.statSync(tempPath).size;
                if (compressedSize < originalSize) {
                    fs.renameSync(tempPath, filePath);
                    outputPath = filePath;
                } else {
                    fs.unlinkSync(tempPath);
                    return {
                        filePath,
                        fileName,
                        originalSize,
                        compressedSize: originalSize,
                        savedPercent: '0',
                        success: false,
                        error: '压缩后文件更大，保持原文件'
                    };
                }
            } else {
                // 输出到新目录
                if (options.preserveStructure) {
                    const relativePath = path.relative(options.sourceRoot, path.dirname(filePath));
                    outputPath = path.join(options.outputRoot, relativePath, fileName);
                } else {
                    outputPath = path.join(options.outputRoot, fileName);
                }
                
                const outputDir = path.dirname(outputPath);
                if (!fs.existsSync(outputDir)) {
                    fs.mkdirSync(outputDir, { recursive: true });
                }
                
                await source.toFile(outputPath);
            }

            const compressedSize = fs.statSync(outputPath).size;
            const savedPercent = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);

            return {
                filePath,
                fileName,
                originalSize,
                compressedSize,
                savedPercent,
                success: true
            };

        } catch (error: any) {
            return {
                filePath,
                fileName,
                originalSize,
                compressedSize: 0,
                savedPercent: '0',
                success: false,
                error: error.message || 'API 压缩失败'
            };
        }
    }

    private async compressWithCrawler(filePath: string, options: CompressionOptions): Promise<FileResult> {
        const fileName = path.basename(filePath);
        const ext = path.extname(filePath).toLowerCase();
        const fileType = ext === '.png' ? 'image/png' : ext === '.webp' ? 'image/webp' : 'image/jpeg';
        
        const originalSize = fs.statSync(filePath).size;
        let retryCount = 0;
        const maxRetries = 2;

        while (retryCount <= maxRetries) {
            try {
                // 1. 读取文件
                const fileData = fs.readFileSync(filePath);
                const headers = this.getRandomHeaders();

                // 2. 上传文件
                const storeRes = await axios.post(this.TINY_API.STORE_URL, fileData, {
                    headers: {
                        ...headers,
                        'Content-Type': 'application/octet-stream',
                        'Content-Length': originalSize,
                    },
                    timeout: 30000
                });

                // 3. 获取文件 key
                const key = storeRes.headers['location'].split('/').pop();

                // 4. 请求压缩
                const processRes = await axios.post(this.TINY_API.PROCESS_URL, {
                    key,
                    originalSize,
                    originalType: fileType,
                }, {
                    headers: {
                        ...headers,
                        'Content-Type': 'application/json',
                    },
                    timeout: 30000
                });

                // 5. 下载压缩后的文件
                const downloadRes = await axios.get(processRes.data.url, {
                    responseType: 'arraybuffer',
                    timeout: 30000
                });

                let outputPath: string;

                if (options.replaceOriginal) {
                    // 替换模式
                    const tempPath = filePath + '.tmp';
                    fs.writeFileSync(tempPath, downloadRes.data);
                    
                    const compressedSize = fs.statSync(tempPath).size;
                    if (compressedSize < originalSize) {
                        fs.renameSync(tempPath, filePath);
                        outputPath = filePath;
                    } else {
                        fs.unlinkSync(tempPath);
                        return {
                            filePath,
                            fileName,
                            originalSize,
                            compressedSize: originalSize,
                            savedPercent: '0',
                            success: false,
                            error: '压缩后文件更大，保持原文件'
                        };
                    }
                } else {
                    // 输出到新目录
                    if (options.preserveStructure) {
                        const relativePath = path.relative(options.sourceRoot, path.dirname(filePath));
                        outputPath = path.join(options.outputRoot, relativePath, fileName);
                    } else {
                        outputPath = path.join(options.outputRoot, fileName);
                    }
                    
                    const outputDir = path.dirname(outputPath);
                    if (!fs.existsSync(outputDir)) {
                        fs.mkdirSync(outputDir, { recursive: true });
                    }
                    
                    fs.writeFileSync(outputPath, downloadRes.data);
                }

                const compressedSize = fs.statSync(outputPath).size;
                const savedPercent = ((originalSize - compressedSize) / originalSize * 100).toFixed(1);

                return {
                    filePath,
                    fileName,
                    originalSize,
                    compressedSize,
                    savedPercent,
                    success: true
                };

            } catch (error: any) {
                retryCount++;
                
                if (retryCount > maxRetries) {
                    let errorMessage = '爬虫压缩失败';
                    if (error.response) {
                        errorMessage = `HTTP ${error.response.status}`;
                    } else if (error.request) {
                        errorMessage = '网络请求失败';
                    } else {
                        errorMessage = error.message || '未知错误';
                    }

                    return {
                        filePath,
                        fileName,
                        originalSize,
                        compressedSize: 0,
                        savedPercent: '0',
                        success: false,
                        error: errorMessage
                    };
                }

                // 重试前等待
                await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
            }
        }

        return {
            filePath,
            fileName,
            originalSize,
            compressedSize: 0,
            savedPercent: '0',
            success: false,
            error: '重试次数用尽'
        };
    }

    private async switchToNextKey(apiKeys: string[]): Promise<boolean> {
        if (!tinify || this.currentKeyIndex >= apiKeys.length - 1) {
            return false;
        }

        this.currentKeyIndex++;
        tinify.key = apiKeys[this.currentKeyIndex];

        try {
            await tinify.validate();
            return true;
        } catch (error) {
            return await this.switchToNextKey(apiKeys);
        }
    }

    private getRandomHeaders() {
        return {
            'User-Agent': randomUseragent.getRandom(),
            ...this.TINY_API.HEADERS
        };
    }

    private getAllImageFiles(dir: string): string[] {
        const results: string[] = [];
        
        if (path.basename(dir).startsWith('.')) {
            return results;
        }

        try {
            const files = fs.readdirSync(dir);
            for (const file of files) {
                const fullPath = path.join(dir, file);
                const stat = fs.statSync(fullPath);
                
                if (stat.isDirectory()) {
                    results.push(...this.getAllImageFiles(fullPath));
                } else {
                    const ext = path.extname(file).toLowerCase();
                    if (this.SUPPORTED_FORMATS.includes(ext)) {
                        results.push(fullPath);
                    }
                }
            }
        } catch (error) {
            console.warn(`无法读取目录 ${dir}:`, error);
        }

        return results;
    }

    private copyUnsupportedFiles(sourceDir: string, outputDir: string, preserveStructure: boolean) {
        if (path.basename(sourceDir).startsWith('.')) {
            return;
        }

        try {
            const files = fs.readdirSync(sourceDir);
            for (const file of files) {
                const fullPath = path.join(sourceDir, file);
                const stat = fs.statSync(fullPath);
                
                if (stat.isDirectory()) {
                    this.copyUnsupportedFiles(fullPath, outputDir, preserveStructure);
                } else {
                    const ext = path.extname(file).toLowerCase();
                    if (!this.SUPPORTED_FORMATS.includes(ext)) {
                        let outputPath: string;
                        if (preserveStructure) {
                            const relativePath = path.relative(sourceDir, path.dirname(fullPath));
                            outputPath = path.join(outputDir, relativePath, file);
                        } else {
                            outputPath = path.join(outputDir, file);
                        }
                        
                        const outputFileDir = path.dirname(outputPath);
                        if (!fs.existsSync(outputFileDir)) {
                            fs.mkdirSync(outputFileDir, { recursive: true });
                        }
                        
                        fs.copyFileSync(fullPath, outputPath);
                    }
                }
            }
        } catch (error) {
            console.warn(`复制非图片文件失败 ${sourceDir}:`, error);
        }
    }

    private formatFileSize(bytes: number): string {
        if (bytes === 0) return '0 B';
        const k = 1024;
        const sizes = ['B', 'KB', 'MB', 'GB'];
        const i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
}